package evolutionaryballoons.behavior.genetic;

import static evolutionaryballoons.Configuration.sHeight;
import static evolutionaryballoons.Configuration.sMaxLift;
import static evolutionaryballoons.Configuration.sMinLift;
import static evolutionaryballoons.Configuration.sNumberOfCurrents;
import static evolutionaryballoons.Configuration.sWidth;

import java.awt.geom.Point2D;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import evolutionaryballoons.Balloon;
import evolutionaryballoons.Currents;
import evolutionaryballoons.Points;

public class DataProvider {
	private Balloon balloon;
	private Points points;
	private Currents currents;
	private Random random;
	
	private List<Integer> getClosestCurrents(boolean favorable) {
		List<Integer> winds = new LinkedList<Integer>();
		
		// Prepare
		
		Point2D p = points.get(balloon.targetPoint);
		
		if (p == null) {
			return winds; 
		}
		
		boolean isLeft = p.getX() - balloon.x < 0;
		
		if (!favorable) isLeft = !isLeft;
		
		// Find favorable winds
		
		for (int i = 0; i < sNumberOfCurrents; i++) {
			boolean isNegative = currents.get(i) < 0.0f;
			
			if (isLeft == isNegative) {
				winds.add(i);
			}
		}
		
		Collections.sort(winds, new ClosestCurrentComparator());
		
		return winds;
	}
	
	private double convertCurrentIndexToY(int index) {
		return (index + 0.5) * sHeight / sNumberOfCurrents;
	}
	
	class ClosestCurrentComparator implements Comparator<Integer> {
		@Override
		public int compare(Integer o1, Integer o2) {
			double y1 = convertCurrentIndexToY(o1);
			double y2 = convertCurrentIndexToY(o2);
			
			double dy1 = (int) Math.abs(y1 - balloon.y);
			double dy2 = (int) Math.abs(y2 - balloon.y);
			
			return Double.compare(dy1, dy2);
		}
	}
	
	public DataProvider(Balloon balloon, Points points, Currents currents) {
		this.balloon = balloon;
		this.points = points;
		this.currents = currents;
		this.random = new Random();
	}
	
	public float getData(Sources source) {
		Point2D p;
		List<Integer> winds;
		double y;
		
		balloon.cost += source.cost();
		
		switch(source) {
			case CurrentWindStrength:
				int iy = (int) balloon.y * sNumberOfCurrents / sHeight;
				
				return currents.get(iy);
			case DistanceToClosestFavorableWind:
				winds = getClosestCurrents(true);
				
				if (winds.size() == 0) {
					return 0.0f;
				} else {
					y = convertCurrentIndexToY(winds.get(0));
					
					return (float) ((y - balloon.y) / sHeight);
				}
				
			case PositionOfClosestFavorableWind:
				winds = getClosestCurrents(true);
				
				if (winds.size() == 0) {
					return 0.0f;
				} else {
					y = convertCurrentIndexToY(winds.get(0));
					
					return (float) (y - (sHeight / 2)) / (sHeight / 2);
				}
				
			case DistanceToClosestUnfavorableWind:
				winds = getClosestCurrents(false);
				
				if (winds.size() == 0) {
					return 0.0f;
				} else {
					y = convertCurrentIndexToY(winds.get(0));
					
					return (float) ((y - balloon.y) / sHeight);
				}
				
			case PositionOfClosestUnfavorableWind:
				winds = getClosestCurrents(false);
				
				if (winds.size() == 0) {
					return 0.0f;
				} else {
					y = convertCurrentIndexToY(winds.get(0));
					
					return (float) (y - (sHeight / 2)) / (sHeight / 2);
				}
				
			case DistanceToClosestPointHorizontally:
				p = points.get(balloon.targetPoint);
				
				if (p == null) {
					return 0.0f;
				} else {
					return (float) ((p.getX() - balloon.x) / sWidth);
				}
				
			case DistanceToClosestPointVertically:
				p = points.get(balloon.targetPoint);
				
				if (p == null) {
					return 0.0f;
				} else {
					return (float) ((p.getY() - balloon.y) / sHeight);
				}
				
			case BurnerStatus:
				return (balloon.burnerOn ? 1.0f : -1.0f);
				
			case Lift:
				if (balloon.lift > 0) {
					return (balloon.lift / sMaxLift);
				} else {
					return -(balloon.lift / sMinLift);
				}
				
			case HorizontalPosition:
				return (float) (balloon.x - (sWidth / 2)) / (sWidth / 2);
				
			case VerticalPosition:
				return (float) (balloon.y - (sHeight / 2)) / (sHeight / 2);
			
			case Random:
				return random.nextFloat() * 2.0f - 1.0f;
				
			default:
				return 0.0f;
		}
	}
}
